Fork me on GitHub

Re0:从零开始的JavaScript - 浅谈 Promise 实现

前言

不知道Promise是啥的,请看上一篇《异步流程控制》

image

看了不少Prmose实现的资料,目前的理解就是Promise也就是回调的一种优雅实现。

现在Javascript的重心都放在了async-await,而async-await是建立在Promise之上的,所以深入一下Promise,还是很有必要的。

实现功能分析

原生Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
new Promise(function (resolve,reject){
setTimeout(function (){
console.log(1)
resolve(2)
},1000)
}).then(function (val){
setTimeout(function (){
console.log('2:'+val)
},1000)
}).then(function (val){
setTimeout(function (){
console.log('3:'+val)
},1000)
})
// 1
// 2:2
// 3:und

开始实现

image

创建一个_promise函数,由简到繁

简单功能

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function _promise(fn){
//resolve时的回调
var callback;
//一个实例的方法,用来注册异步事件
this.then = function(done){
callback = done;
}
function resolve(val){
callback(val);
}
fn(resolve);
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new _promise(function (resolve){
setTimeout(function (){
console.log(1)
resolve(2)
},1000)
}).then(function (val){
setTimeout(function (){
console.log('2:'+val)
},1000)
})
// 1
// 2:2

步骤分析:

  1. 传入fn执行,并调用then方法传入异步执行完毕后的函数赋值给Promise内部的callback
  2. Promise内部fn执行并将resolve函数作为参数传入
  3. fn执行到成功位置,执行resolve函数并传入成功值(val)
  4. resolve函数执行回调callback并传入成功值

链式支持

实现:

在then中返回this,并把then的回调存放到$resolve数组中,执行resolve使用forEach依次调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function _promise(fn){
// resolve时的回调
var promise = this,
value = null,
promise.$resolve = [] // 存放需要调用的函数
// 返回this,支持链式调用
this.then = function (onFulfilled){
promise.$resolve.push(onFulfilled)
return this
}
// 成功回调,把$resolve存放的函数依次执行
function resolve(value){
promise.$resolve.forEach(function (callback){
callback(value)
})
}
fn(resolve)
}

可是如果then中的传入函数是同步的话,就无法控制回调执行顺序

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
new _promise(function (resolve){
for(var i = 0,num; i < 1000;i++){
num=Math.random()*100
}
console.log(1)
resolve(2)
}
}).then(function (val){
setTimeout(function (){
console.log('2:'+val)
},1000)
}).then(function (val){
setTimeout(function (){
console.log('3:'+val)
},1000)
})
// 1

只会执行fn,回调都没有执行,因为这时在resolve比then先执行,这时候promise.$resolve数组是空数组,所以上面代码只打印了一个1。我们需要把resolve放到下一个任务队列末尾执行,也就是then的回调都添加到了数组中之后

修改resolve函数

1
2
3
4
5
6
7
function resolve(value){
setTimeout(function (){
promise.$resolve.forEach(function (callback){
callback(value)
})
},0)
}

引入状态

分析:

每个 Promise 存在三个互斥状态:pending、fulfilled、rejected。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。

源码:

promise.value保存之后调用该对象回调的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function _promise(fn){
// 成功的回调
var promise = this
promise.value = null
promise.$resolve = [],
promise.$status = 'PENDING'
this.then = function (onFulfilled){
if(promise.$status === 'PENDING'){
promise.$resolve.push(onFulfilled)
return this
}
onFulfilled(promise.value)
return this
}
function resolve(value){
setTimeout(function (){
promise.$status = "FULFILLED";
promise.$resolve.forEach(function (callback){
callback(value)
promise.value = value
})
},0)
}
fn(resolve)
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = new _promise(function (resolve){
setTimeout(function (){
console.log('1:')
resolve(2)
},1000)
})
a.then(function (val){
console.log('2:'+val)
})
// 1
// 2:2

异步回调返回的结果传递到下一个回调

修改resolve函数

1
2
3
4
5
6
7
8
9
function resolve(value){
setTimeout(function (){
promise.$status = "FULFILLED";
promise.$resolve.forEach(function (callback){
value = callback(value)
promise.value = value
})
},0)
}

即可

失败处理

异步操作不可能都成功,在异步操作失败时,标记其状态为 rejected,并执行注册的失败回调。

有了之前处理 fulfilled 状态的经验,支持错误处理变得很容易。毫无疑问的是,在注册回调、处理状态变更上都要加入新的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
this.then = function (onFulfilled, onRejected){
return new _promise(function (resolve){
function handle(value){
var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value;
resolve(ret)
}
function errback(reason){
reason = typeof onRejected === 'function' && onRejected(reason) || reason;
reject(reason)
}
if(promise.$status === 'PENDING'){
promise.$resolves.push(handle)
promise.$rejects.push(errback)
} else if (promise.$status === 'FULFILLED') {
handle(promise.$value)
} else if (promise.$status === 'REJECTED') {
errback(promise.$reason)
}
})
}
-------------本文结束感谢您的阅读-------------
分享